/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 combat_label.h

	$Header: /heroes4/combat_label.h $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_label.h"

#include <map>
#include "abbreviate_number.h"
#include "battlefield.h"
#include "bitmap_group.h"
#include "bitmap_group_cache.h"
#include "combat_actor.h"
#include "combat_creature.h"
#include "combat_object_type.h"
#include "combat_reader.h"
#include "convert_16_bit.h"
#include "font.h"
#include "game_time.h"
#include "player_color.h"
#include "random.h"
#include "standard_fonts.h"

namespace
{
	struct t_bitmap_array
	{
		t_bitmap_group_cache cache[k_player_color_count];
	};

	typedef std::map<double, t_bitmap_array>		t_cache_array_map;
	typedef std::map<double, t_bitmap_group_cache>	t_cache_map;
};

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
int const k_frame_delay = 1000 / 10;

t_combat_label::t_combat_label( t_battlefield& battlefield ) 
              : t_idle_processor( 0, k_frame_delay ), t_abstract_combat_object(&battlefield),
			    m_battlefield( battlefield )
{
}

t_combat_label::t_combat_label( t_combat_creature& actor, t_player_color color, double scale )
              : t_idle_processor( 0, k_frame_delay ),
			    t_abstract_combat_object( &actor.get_battlefield() ),
				m_battlefield( actor.get_battlefield() )
{
	set_creature( &actor, color, scale );
	actor.set_label( this );
}

void t_combat_label::set_creature( t_combat_creature const* creature, t_player_color color,
								   double scale )
{
	// find the appropriate scaled cache
	static t_cache_array_map	caches;
	t_cache_array_map::iterator	cache = caches.find( scale );
	int                   i;

	m_actor = creature;
	// find background cache
	if (cache == caches.end())
	{
		t_bitmap_array new_cache;
		std::string    cache_name;

		for (i = 0; i < k_player_color_count; i++)
		{
			cache_name = "icons.combat_labels.";
			cache_name += k_player_color_keyword[i];
			new_cache.cache[i] = t_bitmap_group_cache( cache_name, scale );
		}
		caches.insert( std::make_pair( scale, new_cache ) );
		cache = caches.find( scale );
	}
	// get the bitmaps
	m_bitmaps = cache->second.cache[color].get();

	// find health bar cache
	static t_cache_map		health_bar_caches;
	t_cache_map::iterator	health_cache = health_bar_caches.find( scale );

	if (health_cache == health_bar_caches.end())
	{
		t_bitmap_group_cache	new_cache( "icons.combat_labels.health", scale );
		health_cache = health_bar_caches.insert( std::make_pair( scale, new_cache ) ).first;
	}
	m_health_bitmaps = health_cache->second.get();

	// make up a random offset for time, so multiple labels aren't synchronized.
	m_selected = false;

	// find frames of correct color
	for (i = 0; i < m_bitmaps->size(); i++)
	{
		if (strnicmp((*m_bitmaps)[i].get_name().c_str(), "frame", 5 ) == 0)
			m_frames[0].push_back( i );
		if (strnicmp((*m_bitmaps)[i].get_name().c_str(), "selected", 8 ) == 0)
			m_frames[1].push_back( i );
	}

	m_origin.x = 0;
	m_origin.y = 0;
	m_origin = -get_rect().size() / 2;
	m_color = convert_to_16_bit( t_pixel_24(255,255,255) );
	m_text_rect = m_bitmaps->find( "text" )->get_rect();
	m_font = get_font( m_text_rect.height() );

	m_health_background = m_health_bitmaps->find( "background" );
	m_health_bar = m_health_bitmaps->find( "health_bar" );
	m_mana_bar = m_health_bitmaps->find( "mana_bar" );
	m_health_origin = m_bitmaps->find( "health_bar" )->get_rect().top_left();

	set_frame( 0 );
}

// -----------------------------------------------------------------------
// Labels never block movement
// -----------------------------------------------------------------------
bool t_combat_label::blocks_movement() const
{
	return false;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
bool t_combat_label::is_animated() const
{
	return true;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
void t_combat_label::draw_shadow_to( t_uint32				current_time,
									 t_screen_rect const &	source_rect,
									 t_abstract_bitmap16 &	dest,
									 t_screen_point const &	dest_point ) const
{
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
void t_combat_label::draw_shadow_to( t_uint32				current_time,
									 t_abstract_bitmap16 &	dest,
									 t_screen_point const &	dest_point ) const
{
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
void t_combat_label::set_frame( int frame )
{
	m_frame = frame;
	if (m_frame >= m_frames[m_selected].size())
		m_frame = 0;
	m_bitmap_number = m_frames[m_selected][m_frame];
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
void t_combat_label::on_idle()
{
	t_battlefield& battlefield = m_battlefield;

	battlefield.invalidate( *this );
	set_frame( m_frame + 1 );
	battlefield.invalidate( *this );
}


void t_combat_label::draw_status_bar( int value, int limit_value, t_bitmap_layer const* bar,
									  t_screen_rect const& client_source_rect,
									  t_abstract_bitmap16 &	 dest,
									  t_screen_point const& dest_point ) const
{
	t_screen_rect	rect;
	int				width;

	if ( limit_value < 1 )
		limit_value = 1;

	if (value > limit_value)
		value = limit_value;

	rect = bar->get_rect() + m_health_origin;
	width = value * rect.width() / limit_value;
	rect.right = rect.left + width;
	rect = intersection( rect, client_source_rect );
	if (rect.width() > 0 && rect.height() > 0)
	{
		bar->draw_to( rect - m_health_origin,
					  dest, dest_point + rect.top_left() + m_origin, get_alpha() );
	}
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------

void t_combat_label::draw_to( t_uint32				 current_time,
							  t_screen_rect const &	 source_rect,
							  t_abstract_bitmap16 &	 dest,
							  t_screen_point const & dest_point ) const
{
	t_screen_rect			rect;
	t_bitmap_layer const&	layer = (*m_bitmaps)[m_bitmap_number];
	t_hero const*			hero = m_actor->get_hero();
	t_screen_rect			client_source_rect = source_rect - m_origin;

	layer.draw_to( client_source_rect, dest, 
		            dest_point + source_rect.top_left(), get_alpha() );
	if (hero == 0)
	{
		std::string	text = abbreviate_number( m_actor->get_number(), *m_font, 
											  m_text_rect.width() );
		int			width = m_font->get_width( text.c_str() );

		rect = m_text_rect + dest_point + m_origin;
		rect.left += (rect.width() - width) / 2;
		m_font->draw_to( dest, rect, text.c_str(), m_color );
	}
	else
	{
		int				max_hits = m_actor->get_hit_points();
		int				current_hits = m_actor->get_hit_points() - m_actor->get_wounds();
		int				max_spell_points = m_actor->get_maximum_spell_points();
		int				current_spell_points = m_actor->get_spell_points();
		t_screen_point	origin = m_origin + m_health_origin;
		
		if (current_spell_points > max_spell_points)
			current_spell_points = max_spell_points;
		m_health_background->draw_to( source_rect - origin, dest,
									  dest_point + source_rect.top_left(),
									  get_alpha() );
		draw_status_bar( current_hits, max_hits, m_health_bar, client_source_rect, dest, dest_point );
		draw_status_bar( current_spell_points, max_spell_points, m_mana_bar, client_source_rect,
						 dest, dest_point );
	}
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
void t_combat_label::draw_to( t_uint32				 current_time,
							  t_abstract_bitmap16 &	 dest,
							  t_screen_point const & dest_point ) const
{
	t_screen_rect rect = get_rect(current_time);

	draw_to( current_time, rect, dest, dest_point );
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
int t_combat_label::get_footprint_size() const
{
	return 0;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
int t_combat_label::get_height() const
{
	return 0;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// get complete extent of all rectangles
// -----------------------------------------------------------------------
t_screen_rect t_combat_label::get_rect() const
{
	int           i;
	t_screen_rect result;

	result = (*m_bitmaps)[m_frames[m_selected][0]].get_rect();
	for (i = 1; i < m_frames[m_selected].size(); i++)
	{
		result = get_extent( result, (*m_bitmaps)[m_frames[m_selected][i]].get_rect() );
	}
	return result + m_origin;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// get extent of current rect
// -----------------------------------------------------------------------
t_screen_rect t_combat_label::get_rect( t_uint32 current_time ) const
{
	return (*m_bitmaps)[m_bitmap_number].get_rect() + m_origin;
}
// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
t_screen_rect t_combat_label::get_shadow_rect() const
{
	return t_screen_rect(0,0,0,0);
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
t_screen_rect t_combat_label::get_shadow_rect( t_uint32 current_time ) const
{
	return t_screen_rect(0,0,0,0);
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
bool t_combat_label::hit_test( t_uint32 current_time,
							   t_screen_point const &	point ) const
{
	return false;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
bool t_combat_label::is_underlay() const
{
	return false;
}

// -----------------------------------------------------------------------
// label for creature stacks in combat
// -----------------------------------------------------------------------
bool t_combat_label::needs_redrawing( t_uint32	last_update_time,
								      t_uint32	current_time ) const
{
	return false;
}

// -----------------------------------------------------------------------
// compute where label should be positioned
// -----------------------------------------------------------------------
t_map_point_3d t_combat_label::compute_position( bool check_overlap ) const
{
	t_map_point_3d result = m_actor->get_footprint_center();
	t_battlefield& battlefield = m_actor->get_battlefield();
	
	result.height += m_actor->get_height() + 52;

	if (check_overlap)
	{
		result = battlefield.compute_label_position( this, result );
	}
	return result;
}

// combat labels are effectively always on top
int t_combat_label::get_depth() const
{
	return t_abstract_combat_object::get_depth() + k_depth_overlay;
}

void t_combat_label::on_placed()
{
	if (m_actor == 0)
		return;
	t_battlefield& battlefield = m_actor->get_battlefield();

	battlefield.add_creature_label( this );
}

void t_combat_label::on_removed()
{
	if (m_actor == 0)
		return;
	t_battlefield& battlefield = m_actor->get_battlefield();
	
	battlefield.remove_creature_label( this );
}

// -----------------------------------------------------------------------
// read/ write
// -----------------------------------------------------------------------
t_combat_object_type t_combat_label::get_object_type() const
{
	return k_combat_object_type_label;
}

static int const k_current_version = 0;

bool t_combat_label::read( t_combat_reader& reader )
{
	std::streambuf& stream = reader.get_stream();
	int				version = get<t_uint16>( stream );
	t_battlefield&	battlefield = reader.get_battlefield();

	if (version < 0 || version > k_current_version)
		return false;
	if (!read_data( stream ))
		return false;

	t_player_color color;

	m_actor = reader.read_creature();
	color = battlefield.get_player_color( m_actor->belongs_to_defender() );
	set_creature( m_actor, color, battlefield.get_model_scale() );
	m_selected = (get<t_uint8>(stream) != 0);
	return true;
}

bool t_combat_label::write( t_combat_writer& writer ) const
{
	std::streambuf& stream = writer.get_stream();

	put<t_uint16>( stream, k_current_version );
	if (!write_data( stream ))
		return false;
	writer.write( m_actor );
	put<t_uint8>( stream, m_selected );
	return true;
}

void t_combat_label::place_during_read( t_battlefield& battlefield )
{
}
